
#import "NewsGroupSetBrowser.h"
#import "Newsgroup.h"
#import "NewsgroupBrowserCell.h"
#import <misckit/MiscAppDefaults.h>
#import "MatrixSet.h"
#import "MatrixScroller.h"
#import "instr.h"
#import "regexpr.h"

@implementation NewsGroupSetBrowser

#define IS_LEAF 1
#define IS_NO_LEAF 2

- awakeFromNib
{
   buttonTitle=NXCopyStringBuffer([selectionButton title]);
   [self setCellPrototype:[[[NewsgroupBrowserCell alloc] init] setNewsgroupCell:nil]];
   return self;
}

- free
{
   if(myVisibleCellList!=nil)
      [myVisibleCellList free];
   free(buttonTitle);
   return [super free];
}

- initFrame:(const NXRect *)frameRect
{
   int minWidth;

   [super initFrame:frameRect];
	
	minWidth=[NXApp defaultIntValue:"MinBrowserColumnWidth"];   
   if(minWidth==0)
      minWidth=70;

   [self setAction:@selector(selectNewsgroup:)];
   [self setTarget:mySet];
   [self setPathSeparator:'.'];
   [self setMultipleSelectionEnabled:NO];
   [self setDelegate:self];
   [self setEmptySelectionEnabled:YES];
   [self setHorizontalScrollButtonsEnabled:NO];
   [self setHorizontalScrollerEnabled:YES];
   [self setMinColumnWidth:minWidth];
   [self setTitled:NO];
   [self reuseColumns:YES];
   [self separateColumns:YES];
	[self useScrollBars:YES];
	[self useScrollButtons:YES];
   listList=[[List alloc] init];

   return self;
}

- (int)browser:sender fillMatrix:matrix inColumn:(int)column
{
   struct bcellDesc{
      int leaf;
      id newsgroup;   
   };
   int i,y,z,from,ii;
   List *aList,*bList,*cList;
   Newsgroup *aNewsgroup;
   const char *name;
   char *end_of_name;
   char *cell_name;
   NXHashState hashState;
   HashTable *aHashTable;
   char *aStringKey;
   char buf[255];
   char *path;
   char isnull[]="";
   struct bcellDesc *c;

   //compute column selection list 
   if(column>0){
      //get selected path
      path=[self getPath:buf toColumn:column];
      NX_ASSERT(path!=NULL,"No path selected");
      path++;

      y=[listList count];
      for(i=y-1;i>=column;i--)
         [[listList removeObjectAt:i] free];
      bList=[[List alloc] init];
      aList=[listList objectAt:column-1];
      z=[aList count];
      for(i=0;i<z;i++){
         const char *lname;
         int plen=strlen(path);
         aNewsgroup=[aList objectAt:i];
         lname=[aNewsgroup stringValue];
         if((strncmp(lname,path,plen)==0) && (lname[plen]=='.'))
            [bList addObject:aNewsgroup];
      }
      [listList addObject:bList];
   }
   else
      path=isnull;

   //compute column entries
   aList=[listList objectAt:column];
   aHashTable=[[HashTable alloc] initKeyDesc:"*" valueDesc:"!"];
   z=[aList count];
   for(i=0;i<z;i++){
      int isLeaf;
      aNewsgroup=[aList objectAt:i];
      name=[aNewsgroup stringValue];
      name+=strlen(path);
      if(path[0]!='\0')
         name++;
   
      end_of_name=strchr(name,'.');
      if(end_of_name==NULL){
         cell_name=NXCopyStringBuffer(name);
         isLeaf=IS_LEAF;
      }
      else{
         int size=strlen(name)-strlen(end_of_name);
         cell_name=(char *)malloc((size+3)*sizeof(char));
         strncpy(cell_name,name,size);
         cell_name[size]='\0';
         isLeaf=IS_NO_LEAF;
      }
      if([aHashTable isKey:(void *)cell_name]){
         c=(struct bcellDesc *)[aHashTable valueForKey:(void *)cell_name];
         free(cell_name);
      }
      else{
         c=(struct bcellDesc *)calloc(1,sizeof(struct bcellDesc));
         [aHashTable insertKey:(const void *)cell_name value:(void *)c];
      }
      c->leaf|=isLeaf;
      if(isLeaf==IS_LEAF)
         c->newsgroup=aNewsgroup;
   }
   
   //column enties are now in the hashtable
   //now create column matrix
   hashState=[aHashTable initState];
   i=-1;
   while([aHashTable nextState:&hashState key:(const void **)&aStringKey value:(void **)&c]){
      id aCell;
      [matrix addRow];
      i++;
      aCell=[matrix cellAt:i :0];
      [aCell setLeaf:((c->leaf&IS_LEAF)==IS_LEAF)];
      [aCell setStringValueNoCopy:aStringKey shouldFree:YES];
      if(c->newsgroup!=nil)
         [aCell setNewsgroupCell:c->newsgroup];
      if(c->leaf==3){
         [matrix addRow];
         i++;
         aCell=[matrix cellAt:i :0];
         [aCell setLeaf:NO];
         [aCell setStringValue:aStringKey];
      }
      free(c);
   }
   i++;
   
   //simple sorting :-(
   cList=[matrix cellList];
   for(from=0;from<i-1;from++){
      int min=from;
      id minObject=[cList objectAt:from];
      const char *minString=[minObject stringValue];      
      for(ii=from+1;ii<i;ii++){
         int c;
         id anObject=[cList objectAt:ii];
         const char *aString=[anObject stringValue];
         if( ((c=strcmp(aString,minString))<0) ||
             (c==0 && [anObject isLeaf]) ){
            minObject=anObject;
            minString=aString;
            min=ii;
         }
      }
      if(min!=from){
         id anObject=[cList replaceObjectAt:from with:[cList objectAt:min]];
         [cList replaceObjectAt:min with:anObject];
      }
   }
         
   [aHashTable free];
   return i;
}

- loadMatrix
{
   [self makeVisibleCellList:self];
   [self loadColumnZero];

   return self;
}

- reloadMatrix
{
   char path_buf[255];
   id curSel;

   [[self window] disableFlushWindow];
   [self setAutodisplay:NO];

   curSel=[mySet currentSelection];

   [self getPath:path_buf toColumn:[self lastColumn]];
   
   [self loadMatrix];

   //reselect path or path+cell
   if(curSel==nil){
      if(path_buf[0]!='\0'){
         int i,j,z;
         z=strlen(path_buf)-1;
         j=[myVisibleCellList count];
         //see if there's a visible group with its prefix==path_buf 
         for(i=0;i<j;i++)
            if(strncmp([[myVisibleCellList objectAt:i] stringValue],path_buf+1,z)==0){
               strcat(path_buf,".");
               [self setPath:path_buf];
               break;
            }
      }
   }
   else if([curSel isTaged]==TRUE){
      const char *gname=[curSel stringValue];
      char *newPath;

      newPath=(char *)malloc((strlen(gname)+2)*sizeof(char));
      sprintf(newPath,".%s",gname);
      [self setPath:newPath];
      free(newPath);
   }

   [[self setAutodisplay:YES] display];
   [[[self window] reenableFlushWindow] flushWindow];
   return self;
}

- setMatrixCellList:(List *)aList
{
   myCellList=aList;
   return self;
}


- (List *)cellList
{
   return myVisibleCellList;
}

- removeInvalidCell:aCell;
{
   return [self removeInvalidCell:aCell andUpdate:YES];
}

- removeInvalidCell:aCell andUpdate:(BOOL)update
{
   int i,j;
   for(i=0,j=[listList count];i<j;i++)
      [[listList objectAt:i] removeObject:aCell];
   [myVisibleCellList removeObject:aCell];
   [[myCellList removeObject:aCell] free];

   if(update==YES)
      [self update];

   return self;
}

- update
{
   [self loadColumnZero];
   [self display];
   [mySet sync];
   [NXApp updateWindows];

   return self;
}

- (List *)getCurrSelections
{
   List *aList=[[List alloc] init];
   id selection;

   selection=[self currSelection];
   if(selection!=nil)
      [aList addObject:selection];
   else {
      int j=[listList count];
      NX_ASSERT(j>0,"Listlist empty");
      if(j>1)
         if([[listList objectAt:j-1] count]>1) //shit! 
            [aList appendList:[listList objectAt:j-1]];
   }
   return aList;
}
   

- currSelection
{
   char buf[255];
   char *newsg_name;
   id aCell=[self selectedCell];
   id selCell=nil;
   List *aList;
   int i,j;

   if(aCell==nil)
      return nil;
   if([aCell isLeaf]==TRUE){
      const char *cellName=[aCell stringValue];
      int lcol=[self lastColumn];
      [self getPath:buf toColumn:lcol];
      
      newsg_name=(char *)malloc((strlen(buf)+strlen(cellName)+3)*sizeof(char));
      newsg_name[0]='\0';
      strcpy(newsg_name,buf);
      strcat(newsg_name,".");
      strcat(newsg_name,cellName);
 
      aList=[listList objectAt:lcol];
      j=[aList count];
      for(i=0;i<j;i++){
         if(strcmp([[aList objectAt:i] stringValue],newsg_name+1)==0){
            selCell=[aList objectAt:i];
            break;
         }
      }
      free(newsg_name);
   }
   return selCell;
}

- makeVisibleCellList:sender
{
   int i,j;

   if(myVisibleCellList!=nil)
      [myVisibleCellList free];
   myVisibleCellList=[[List alloc] init];
   j=[myCellList count];
   for(i=0;i<j;i++){
      id aCell=[myCellList objectAt:i];
      if([aCell isTaged]==TRUE)
         [myVisibleCellList addObject:aCell];
   }

   if([listList count]==0)
      [listList addObject:myVisibleCellList];
   else
      [listList replaceObjectAt:0 with:myVisibleCellList];

   return self;
}

- (id)selectionButton
{
   return selectionButton;
}

- setButtonTitle:(const char *)title
{
   free(buttonTitle);
   [selectionButton setTitle:title];
   buttonTitle=NXCopyStringBuffer(title);

   return self;
}

- _delayedUpdate:sender
{
   [selectionButton setTitle:buttonTitle];
   return self;
}

- updateButtonTitle
{
   [self perform:@selector(_delayedUpdate:) with:self afterDelay:0.0 cancelPrevious:TRUE];  
   return self;
}

- (BOOL)selectNextCell:(int)delta inColumn:(int)col leafSelected:(BOOL *)sel
{
   id matrix=[self matrixInColumn:col];
   id aCell=[matrix selectedCell];
   id cellList=[matrix cellList];
   int j,jj;
   int i=0;
   char buf[255];
   id newCell;

   [matrix getNumRows:&j numCols:&jj];

   *sel=FALSE;
   if(j==0)
      return FALSE;
   //find index of cell to select
   if(aCell==nil){
      if(delta>0)
         i=0;
      else
         i=j-1;
   }
   else{
      int pos=[cellList indexOf:aCell];
      if(((delta>0)&&(pos==j-1))||((delta<0)&&(pos==0)))
         return FALSE; 
      i=pos+delta;
   }

   //compose and set path
   [self getPath:buf toColumn:col];
   newCell=[cellList objectAt:i];
   if(![newCell isLeaf]){
      strcat(buf,".");
      strcat(buf,[newCell stringValue]);
      strcat(buf,".");
      [self setPath:buf];
   }
   else{
      strcat(buf,".");
      strcat(buf,[newCell stringValue]);
      [self setPath:buf];
      *sel=TRUE;
   }

   return TRUE;
}

- (BOOL)selectNextCell:(int)delta
{
   int i;
   BOOL r,rr;

   NX_ASSERT(((delta==1) || (delta==-1)),"Wrong parameter selectNextCell");
   
   for(i=[self lastColumn];i>=0;i--){
      r=[self selectNextCell:delta inColumn:i leafSelected:&rr];
      if(rr==TRUE){
         [self sendAction];
         return TRUE;
      }
      if(r==TRUE)
         i=[self lastColumn]+1;
   }

   return FALSE;
}
       
- setPath:(const char *)aPath
{
   char *newPath,*c;
   id bmatrix;
   int i,j;

   NX_ASSERT(*aPath==(char)pathSeparator,"setPath: wrong pathstring");

   if(aPath[strlen(aPath)-1]==(char)pathSeparator){
      if([self selectedCell]!=nil)
         [[self matrixInColumn:[self lastColumn]] selectCellAt:-1 :-1];
      return [super setPath:aPath];
   }

   newPath=NXCopyStringBuffer(aPath);

   c=strrchr(newPath,pathSeparator);
   NX_ASSERT(c!=NULL,"setPath: internal error");

   *c='\0';
   if(*newPath=='\0')
      [super setPath:"."];
   else if([super setPath:newPath]==nil)
      return nil;
   c++;

   bmatrix=[self matrixInColumn:[self lastColumn]];
   [bmatrix getNumRows:&j numCols:&i];
   for(i=0;i<j;i++){
      id aCell=[bmatrix cellAt:i :0];
      if(!strcmp([aCell stringValue],c))
         if([aCell isLeaf]){
            [bmatrix selectCellAt:i :0];
            break;
         }
   }
   free(newPath);
   [self scrollColumnToVisible:[self lastColumn]];
   [self scrollSelectionToVisible];

   if(i==j)
      return nil;
   return self;
}

- scrollSelectionToVisible
{
   id aMatrix=[self matrixInColumn:[self lastVisibleColumn]];
   id aCell=[aMatrix selectedCell];
   if(aCell!=nil)
      [aMatrix scrollCellToVisible:[[aMatrix cellList] indexOf:aCell]
               upperOffset:1.5 lowerOffset:1.5];
   
   return self;
}
//-------------------------
// searchable text protocol
//-------------------------

- (oneway void)makeSelectionVisible
{
}
 
- (int)replaceAll:(const char *)pattern with:(const char *)replacement mode:(SearchMode)mode regexpr:(BOOL)regexpr cases:(BOOL)cases
{
   return SEARCH_CANNOT_WRITE;
}

- (oneway void)replaceSelection:(const char *)replacement
{
}

- (const char *)stringValueForCellAt:(int)index
{
   return [[myVisibleCellList objectAt:index] stringValue];
}

- (int)searchFor:(const char *)pattern mode:(SearchMode)mode reverse:(BOOL)rev regexpr:(BOOL)regexpr cases:(BOOL)cases position:(out int *)pos size:(out int *)size
{
   int ccount;
   int s_pos;
   int fStart,fEnd;
   int delta;
   List *selList;

   unsigned char fm[256], tr[256];
   struct re_pattern_buffer rpat;

   s_pos=-2;
   fStart=fEnd=-2;

   ccount=[myVisibleCellList count];
   if(ccount==0)
      return 0;

   // find first selected cell
   selList=[self getCurrSelections];
   if([selList count]>0)
      s_pos=[myVisibleCellList indexOf:[selList objectAt:0]];
   [selList free];

   if(!rev){
      if(s_pos<0){ 
         fStart=0; fEnd=ccount; 
      }
      else{
         fStart=s_pos+1; fEnd=s_pos+ccount; 
      }
   }
   else{
      if(s_pos<0){ 
         fStart=ccount-1; fEnd=-1; 
      }
      else{
         fStart=s_pos-1+ccount; fEnd=s_pos+1; 
      }
   }
   
   delta= rev? -1:1;
  
   if(regexpr){
      char *str;
      int i;

      memset(&rpat, 0, sizeof(rpat));
      for(i=256; i--;)
         tr[i] = i;
      if(!cases)
         for(i='A'; i<='Z'; i++)
            tr[i] = i-'A'+'a';
      rpat.translate = tr;
      rpat.fastmap = fm;
      str = re_compile_pattern((char *)pattern,strlen(pattern), &rpat);
      if (str!=NULL)
        return (strcmp(str, "Out of memory")?SEARCH_INVALID_REGEXPR:SEARCH_INTERNAL_ERROR);
   }

   for(;fStart!=fEnd;fStart+=delta){
      int index=fStart%ccount;
      const char *result=NULL;
      const char *cellString=[self stringValueForCellAt:index];

      if(regexpr){
        int l=strlen(cellString);
        int p=re_search_pattern(&rpat,(char *)cellString,l,0,l,0);

        if(p==-2)
           return SEARCH_INTERNAL_ERROR;
        result= (p==-1)? NULL : cellString;
      }
      else
         result=instr(cellString,pattern,cases);

      if(result!=NULL){
         *pos=index;
         *size=1;
         return 1;
      }
   }
   
   return 0;
}

- (oneway void)selectTextFrom:(int)start to:(int)end
{
   if(start<=end){
      if(start==end){
         [super setPath:"."];
         [self sendAction];
      }
      else{
         if(start<[myVisibleCellList count]){
            id aCell=[myVisibleCellList objectAt:start];
            char *newPath=malloc((strlen([aCell stringValue])+2)*sizeof(char));
            sprintf(newPath,".%s",[aCell stringValue]);
            [self setPath:newPath];
            free(newPath);
            [self sendAction];
         }
      }
   }
}
      
- (void)writeSelectionToPasteboard:(in Pasteboard *)pboard asType:(in NXAtom)type
{
   id aCell=[self currSelection];

   if(aCell!=nil){
      const char *sval=[aCell stringValue];
      [pboard declareTypes:&type num:1 owner:NULL];
      [pboard writeType:type data:sval length:strlen(sval)];
   }
   return;
}

- mySet
{
   return mySet;
}

- (BOOL)selectNewsgroupNamed:(const char *)gname
{
	int i,j;
	for(i=0,j=[myVisibleCellList count];i<j;i++){		
		const char *name=[[myVisibleCellList objectAt:i] stringValue];
		if(name && !strcmp(gname,name)){
      	char *newPath=malloc((strlen(name)+2)*sizeof(char));
         sprintf(newPath,".%s",name);
         [self setPath:newPath];
			[self scrollSelectionToVisible];
         free(newPath);
         [self sendAction];
			
			return TRUE;
      }
	}
	return FALSE;
}

@end
